"""Module to handle the application menu.
"""
import re

from django.core.cache import cache
from django.db.models import signals

from treemenus.models import Menu, MenuItem

def get_breadcrumb(menu_name, path):
    """Return a list of MenuItem leading to the given path."""
    menu_id = _get_menu_id(menu_name)
    trail = list(_iter_trail(menu_id, path))
    trail.reverse()
    return trail

def is_selected(item, path):
    """Return ``True`` if `item` is selected given the current request `path`.
    """
    id = item.id
    for item2 in _iter_trail(item.menu_id, path):
        if item2.id == id:
            return True

    return False

def minimize_path(menu_id, path):
    """Return the path of a MenuItem that better represents `path`."""
    rex = _get_prefix_regexp(menu_id)
    m = rex.match(path)
    if m is not None:
        return m.group(1)
    else: # shouldn't happen
        return '/'

def _iter_trail(menu_id, path):
    """Iterate over the selected menu items.

    Iterate from the selected menu item to the root (excluded).
    """
    path = minimize_path(menu_id, path)
    items = _get_items_map(menu_id)

    # Find the selected menu, and bail out if the url is not managed.
    item = items.get(path)
    if item is None:
        return

    while 1:
        yield item
        id = item.parent_id
        item = items[id]

        # Don't emit the root item
        if item.parent_id == None:
            return


# Handling of menu cache

# Map Menu.id -> prefix regexp
_prefix_regexp_cache = {}

# Map Menu.id -> { MenuItem.url or MenuItem.id } -> MenuItem
_items_map_cache = {}

# Map menu_name -> menu_id
_menu_id_cache = {}

def _get_prefix_regexp(menu_id):
    """Return a regular expression to detect the related menu name.

    The regexp takes a path and returns in a match the longest trailing path
    for which the menu has a menu item.
    """
    try:
        return _prefix_regexp_cache[menu_id]
    except KeyError:
        _build_menu_cache(menu_id)
        return _prefix_regexp_cache[menu_id]

def _get_items_map(menu_id):
    try:
        return _items_map_cache[menu_id]
    except KeyError:
        _build_menu_cache(menu_id)
        return _items_map_cache[menu_id]

def _get_menu_id(menu_name):
    try:
        return _menu_id_cache[menu_name]
    except KeyError:
        menu = Menu.objects.get(name=menu_name)
        id = _menu_id_cache[menu.name] = menu.id
        return id

def _build_menu_cache(menu_id):
    items = MenuItem.objects.filter(menu__id=menu_id) 
    _prefix_regexp_cache[menu_id] = _make_menu_regexp(items)
    _items_map_cache[menu_id] = _make_items_map(items)

def _make_menu_regexp(items):
    """Generate the regular expression to look for path prefixes."""
    # Get all the URLs for which we have a menu item
    urls = [ i.url for i in items
            if i.url.startswith('/') and i.url.endswith('/') ]

    if urls:    # if not local link
        # Sort them and ensure we include /
        urls.sort(reverse=True)
        if urls[-1] != '/':
            urls.append('/')

    # TODO: with some tree magic we may build a more compact regexp
    p = "(%s)" % "|".join(re.escape(url) for url in urls)
    return re.compile(p)

def _make_items_map(items):
    """Build a map from the url/id to the menu item."""
    rv = {}
    for i in items:
        rv[i.id] = i
        if i.url: rv[i.url] = i

    return rv


# invalidate the above result when the menu is modified
def clear_cache_menu(sender, instance, **kwargs):
    _clear_cache(instance.id)

def clear_cache_menu_item(sender, instance, **kwargs):
    _clear_cache(instance.menu_id)

def _clear_cache(menu_id):
    _prefix_regexp_cache.pop(menu_id, None)
    _items_map_cache.pop(menu_id, None)
    for k, v in _menu_id_cache.iteritems():
        if v == menu_id:
            del _menu_id_cache[k]
            break

# Invalidate the cache every time a Menu or MenuItem is updated.
signals.post_save.connect(clear_cache_menu, sender=Menu)
signals.post_save.connect(clear_cache_menu_item, sender=MenuItem)
signals.post_delete.connect(clear_cache_menu_item, sender=MenuItem)
